package co.edu.unal.bioingenium.kbmed.knowledge.mapping.impl;

import co.edu.unal.bioingenium.kbmed.config.Configuration;
import co.edu.unal.bioingenium.kbmed.knowledge.mapping.api.Mapping;
import co.edu.unal.bioingenium.kbmed.knowledge.mapping.heuristic.Matching;
import co.edu.unal.bioingenium.kbmed.knowledge.mapping.vo.DescriptorScore;
import co.edu.unal.bioingenium.kbmed.knowledge.KnowledgeSourceMetaData;
import co.edu.unal.bioingenium.kbmed.knowledge.dao.KnowledgeSourceDAO;
import co.edu.unal.bioingenium.kbmed.knowledge.dao.KnowledgeSourceDAOFactory;
import co.edu.unal.bioingenium.kbmed.text.filter.FilterPipe;
import co.edu.unal.bioingenium.kbmed.text.index.io.InvertedIndexIO;
import co.edu.unal.bioingenium.kbmed.knowledge.mapping.vo.DescriptorScoresMap;
import co.edu.unal.bioingenium.kbmed.knowledge.vo.DescriptorData;
import co.edu.unal.bioingenium.kbmed.text.index.structures.InvertedIndexStrToStr;
import co.edu.unal.bioingenium.kbmed.text.representation.vo.WordList;
import co.edu.unal.bioingenium.kbmed.text.representation.io.WordListIO;
import co.edu.unal.bioingenium.kbmed.text.util.TextUtil;
import ds.tree.RadixTree;
import ds.tree.io.PrefixIndexIO;
import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import uk.ac.shef.wit.simmetrics.similaritymetrics.AbstractStringMetric;

/**
 *
 * @author Alejandro Riveros Cruz Riveros Cruz
 */
public class HardMapping implements Mapping {

    private FilterPipe tokenPipe;
    private FilterPipe sentencePipe;
    //Index structures
    private RadixTree<Integer> descriptorsPrefixIndex;
    private InvertedIndexStrToStr wordByDescriptorIndex;
    private List<String> sortedWordList;
    //Mapping structures
    private Map<String, DescriptorData> descriptorDataByDescriptorId;
    //Constants
    private double SIMILARITY_THRESHOLD = 0.7;
    private boolean verbose = false;

    /**
     * @return:
     * @param ontologyMetaData 
     */
    public HardMapping() {
    }

    @Override
    public void init() {
        try {
            System.out.print("Load descriptor data...");
            KnowledgeSourceDAO knowledgeSourceDAO = KnowledgeSourceDAOFactory.getKnowledgeSourceDAO();
            descriptorDataByDescriptorId = knowledgeSourceDAO.getDescriptorDataByDescriptorId();
            System.out.print("OK\n");
            knowledgeSourceDAO.close();
            knowledgeSourceDAO = null;
            sentencePipe = new FilterPipe();
            sentencePipe.setRemoveNumbers(true);
            tokenPipe = new FilterPipe();
            System.out.print("Load word by descriptor index...");
            wordByDescriptorIndex = InvertedIndexIO.loadStrToStrInvertedIndex(Configuration.KNOWLEDGE_SOURCE_NAME + "WordByDescriptor.idx");
            System.out.print("OK\n");
            System.out.print("Load descriptors prefix index...");
            descriptorsPrefixIndex = (RadixTree<Integer>) PrefixIndexIO.load(Configuration.KNOWLEDGE_SOURCE_NAME + "DescriptorsWordsPrefix.idx");
            System.out.print("OK\n");
            System.out.print("Load word list...");
            WordList wordList = WordListIO.loadWordList(Configuration.KNOWLEDGE_SOURCE_NAME + "DescriptorsWords.lst");
            sortedWordList = wordList.getSortedWordList();
            System.out.print("OK\n");
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(SoftMapping.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(SoftMapping.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * @param:
     * @return:
     * @Override
     */
    @Override
    public Map<String, List<DescriptorData>> doMapping(String text) {
        Map<String, List<DescriptorData>> results = new HashMap<String, List<DescriptorData>>();
        List<String> sentences = TextUtil.getSentences(text);
        for (String sentence : sentences) {
            results.put(sentence, mapSentence(sentence));
        }
        return results;
    }

    /**
     * @param:
     * @return:
     * @Override
     */
    @Override
    public List<DescriptorData> mapSentence(String sentence) {
        DescriptorScoresMap sentenceScoresMap = buildCandidateDescriptorSet(sentence);
        List<DescriptorData> identifiedConcepts = evaluateCandidates(sentenceScoresMap);
        if (verbose) {
            System.out.println("SENTENCE: " + sentence);
            for (DescriptorData descriptorData : identifiedConcepts) {
                System.out.println(descriptorData);
            }
            System.out.println("\n");
        }
        return identifiedConcepts;
    }

    @Override
    public List<DescriptorData> evaluateCandidates(DescriptorScoresMap descriptorScoresMap) {
        Map<BitSet, DescriptorData> conceptIdentifiedsMap = new HashMap<BitSet, DescriptorData>();
        List<DescriptorData> conceptIdentifieds = new ArrayList<DescriptorData>();
        DescriptorScore descriptorScore;
        Map<BitSet, Double> maxMap = new HashMap<BitSet, Double>();
        for (String descriptorId : descriptorScoresMap.keySet()) {
            descriptorScore = descriptorScoresMap.getDescriptorScore(descriptorId);
            descriptorScore.setDescriptorTokens(descriptorDataByDescriptorId.get(descriptorId).getDescriptorTokens());
            descriptorScore.setTotalScore(Matching.getScore(descriptorScore, descriptorScoresMap.getMaxScores()));
            //Select concepts
            if (descriptorScore.getTotalScore() >= SIMILARITY_THRESHOLD) {
                if (maxMap.containsKey(descriptorScore.getBitSetKey())) {
                    if (maxMap.get(descriptorScore.getBitSetKey()) < descriptorScore.getTotalScore()) {
                        maxMap.put(descriptorScore.getBitSetKey(), descriptorScore.getTotalScore());
                        conceptIdentifiedsMap.put(descriptorScore.getBitSetKey(), descriptorDataByDescriptorId.get(descriptorId));
                    } else {
                        if (maxMap.get(descriptorScore.getBitSetKey()) == descriptorScore.getTotalScore()) {
                            //Ambiguity
                        }
                    }
                } else {
                    maxMap.put(descriptorScore.getBitSetKey(), descriptorScore.getTotalScore());
                    conceptIdentifiedsMap.put(descriptorScore.getBitSetKey(), descriptorDataByDescriptorId.get(descriptorId));
                }
            }
        }
        for (BitSet BitSet : conceptIdentifiedsMap.keySet()) {
            conceptIdentifieds.add(conceptIdentifiedsMap.get(BitSet));
        }
        Collections.sort(conceptIdentifieds);
        return conceptIdentifieds;
    }

    /**
     * @param:
     * @return 
     * @return:
     * @Override
     * @param sentence 
     */
    @Override
    public DescriptorScoresMap buildCandidateDescriptorSet(String sentence) {
        List<String> words = tokenPipe.filter(sentence);
        DescriptorScoresMap sentenceScoresMap = new DescriptorScoresMap(words);
        Set<String> descriptorIds;
        String word = "", prefix = "";
        int chosenIndex, upperBound, lowerBound;
        for (int wordIdx = 0; wordIdx < words.size(); wordIdx++) {
            try {
                word = words.get(wordIdx);
                if (word != null) {
                    if (word.length() > 4) {
                        prefix = word.substring(0, 4);
                    } else {
                        prefix = word;
                    }
                    chosenIndex = descriptorsPrefixIndex.find(prefix);
                    lowerBound = chosenIndex;
                    upperBound = chosenIndex + 1;
                    while (lowerBound > 0 && sortedWordList.get(lowerBound).startsWith(prefix)) {
                        if (word.equalsIgnoreCase(sortedWordList.get(lowerBound))) {
                            descriptorIds = wordByDescriptorIndex.getElement(sortedWordList.get(lowerBound));
                            for (String descriptorId : descriptorIds) {
                                sentenceScoresMap.setDetailedScore(wordIdx, descriptorId, 1.0);
                            }
                        }
                        lowerBound--;
                    }
                    while (upperBound < sortedWordList.size() && sortedWordList.get(upperBound).startsWith(prefix)) {
                        if (word.equalsIgnoreCase(sortedWordList.get(upperBound))) {
                            descriptorIds = wordByDescriptorIndex.getElement(sortedWordList.get(upperBound));
                            for (String descriptorId : descriptorIds) {
                                sentenceScoresMap.setDetailedScore(wordIdx, descriptorId, 1.0);
                            }
                        }
                        upperBound++;
                    }
                }
            } catch (NullPointerException ex) {
//                Logger.getLogger(SoftMapping.class.getName()).log(Level.WARNING, "The prefix " + word + ":" + prefix + " is not present in the ontology index", ex);
            }
        }
        return sentenceScoresMap;
    }

    /**
     * 
     * @return
     */
    @Override
    public double getSIMILARITY_THRESHOLD() {
        return SIMILARITY_THRESHOLD;
    }

    /**
     * 
     * @param SIMILARITY_THRESHOLD
     */
    @Override
    public void setSIMILARITY_THRESHOLD(double SIMILARITY_THRESHOLD) {
        this.SIMILARITY_THRESHOLD = SIMILARITY_THRESHOLD;
    }

    /**
     * 
     * @return
     */
    @Override
    public double getSTRING_METRIC_THRESHOLD() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * 
     * @return
     */
    @Override
    public AbstractStringMetric getStringMetric() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * 
     * @param STRING_METRIC_THRESHOLD
     */
    @Override
    public void setSTRING_METRIC_THRESHOLD(double STRING_METRIC_THRESHOLD) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * 
     * @param verbose
     */
    @Override
    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }
}
